from fastapi import FastAPI, UploadFile, File, BackgroundTasks, Request, Form
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import os
import dotenv
import tempfile
import concurrent.futures
import time
import logging
import pdfplumber
from docx import Document
from langchain_community.document_loaders import CSVLoader
from paddleocr import PaddleOCR
from langchain_community.chat_models import ChatOpenAI
from langchain.schema import HumanMessage
import json
import pandas as pd
import asyncio
import re
from langchain_community.document_loaders import UnstructuredWordDocumentLoader
from processzip import CaseFileProcessor
import uvicorn
from openai import OpenAI

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 加载环境变量
dotenv.load_dotenv()

app = FastAPI()

# 创建全局处理器实例
case_processor = CaseFileProcessor()

# 初始化 PaddleOCR
ocr = PaddleOCR(use_angle_cls=True, lang="ch", use_gpu=False)

# 配置文件上传
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'doc', 'docx', 'pdf', 'png', 'jpg', 'jpeg', 'xls', 'xlsx', 'txt', 'csv', 'json'}
MAX_WORKERS = 4
MAX_CONTENT_LENGTH = 32 * 1024 * 1024  # 32MB

# 确保上传目录存在
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

# 配置静态文件和模板
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")

# 初始化OpenAI客户端
client = OpenAI(
    api_key=os.getenv("SILICON_API_KEY"),
    base_url="https://api.siliconflow.cn/v1",
)


def allowed_file(filename):
    """检查文件扩展名是否允许上传"""
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


async def process_file_async(file):
    """异步处理单个文件"""
    start_time = time.time()
    file_type = file.filename.split('.')[-1].lower()

    # 创建临时文件以供处理
    with tempfile.NamedTemporaryFile(delete=False, suffix=f'.{file_type}') as temp_file:
        # 保存上传的文件内容到临时文件
        file_content = await file.read()
        temp_file.write(file_content)
        temp_file_path = temp_file.name

    try:
        # 使用线程池执行IO密集型文件解析任务
        with concurrent.futures.ThreadPoolExecutor() as executor:
            if file_type in ['docx', 'doc']:
                future = executor.submit(parse_word, temp_file_path)
            elif file_type == 'pdf':
                future = executor.submit(parse_pdf, temp_file_path)
            elif file_type in ['png', 'jpg', 'jpeg']:
                future = executor.submit(parse_image, temp_file_path)
            elif file_type in ['xls', 'xlsx']:
                future = executor.submit(parse_excel, temp_file_path)
            elif file_type == 'csv':
                future = executor.submit(parse_csv, temp_file_path)
            elif file_type == 'json':
                future = executor.submit(parse_json, temp_file_path)
            elif file_type == 'txt':
                future = executor.submit(parse_text, temp_file_path)
            else:
                return f"不支持的文件类型: {file_type}"

            # 等待结果完成
            result = future.result()
    except Exception as e:
        logger.error(f"处理文件 {file.filename} 时出错: {str(e)}")
        result = f"处理文件出错: {str(e)}"
    finally:
        # 清理临时文件
        if os.path.exists(temp_file_path):
            os.unlink(temp_file_path)

    # 重置文件指针
    await file.seek(0)

    elapsed = time.time() - start_time
    logger.info(f"处理文件 {file.filename} 耗时 {elapsed:.2f} 秒")

    return {"filename": file.filename, "content": result}


async def parse_files_async(files):
    """异步处理多个文件"""
    if not files:
        return {"content": "", "message": "未上传文件"}

    # 创建多个异步任务
    tasks = [process_file_async(file) for file in files]

    # 并发执行所有任务
    results = await asyncio.gather(*tasks)

    # 汇总结果
    content = ""
    messages = []

    for result in results:
        file_header = f"===== 文件: {result['filename']} =====\n"
        content += file_header + result['content'] + "\n\n"
        messages.append(f"文件 {result['filename']} 解析完成")

    return {"content": content, "message": "\n".join(messages)}


def parse_text(file_path):
    """解析文本文件"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return f.read()
    except UnicodeDecodeError:
        # 尝试其他编码
        try:
            with open(file_path, 'r', encoding='latin-1') as f:
                return f.read()
        except Exception as e:
            logging.error(f"解析文本文件 {file_path} 时出错: {str(e)}")
            return f"解析文本文件时出错: {str(e)}"


def read_docx_file(filename):
    """使用Document读取docx文件内容"""
    doc = Document(filename)
    content = []
    for paragraph in doc.paragraphs:
        content.append(paragraph.text)
    # 获取表格内容
    for table in doc.tables:
        for row in table.rows:
            row_texts = []
            for cell in row.cells:
                row_texts.append(cell.text)
            if row_texts:
                content.append(" | ".join(row_texts))
    return "\n".join(content)


def parse_word(file_path):
    """解析 Word 文件 (doc/docx)"""
    try:
        file_ext = os.path.splitext(file_path)[1].lower()
        # 对于 .docx 文件使用提供的方法直接解析
        if file_ext == '.docx':
            loader = UnstructuredWordDocumentLoader(file_path)
            docs = loader.load()
            content = "\n".join([doc.page_content for doc in docs])
            return content
        # 对于 .doc 文件，先转换为.docx再解析
        elif file_ext == '.doc':
            loader = UnstructuredWordDocumentLoader(file_path)
            docs = loader.load()
            content = "\n".join([doc.page_content for doc in docs])
            return content
        else:
            return f"不支持的 Word 文件格式: {file_ext}"
    except Exception as e:
        logging.error(f"解析 Word 文档 {file_path} 时出错: {str(e)}")
        return f"解析 Word 文档时出错: {str(e)}"


def parse_csv(file_path):
    """解析 CSV 文件"""
    try:
        loader = CSVLoader(file_path)
        docs = loader.load()
        return "\n".join([doc.page_content for doc in docs])
    except Exception as e:
        logging.error(f"解析 CSV 文件 {file_path} 时出错: {str(e)}")
        return f"解析 CSV 文件时出错: {str(e)}"


def parse_pdf(file_path):
    """解析 PDF 文件"""
    try:
        text = ""
        with pdfplumber.open(file_path) as pdf:
            for page in pdf.pages:
                text += page.extract_text() + "\n"
        return text
    except Exception as e:
        logging.error(f"解析 PDF 文件 {file_path} 时出错: {str(e)}")
        return f"解析 PDF 文件时出错: {str(e)}"


def parse_image(file_path):
    """解析图片文件（使用PaddleOCR）"""
    try:
        # 进行文字识别
        results = ocr.ocr(file_path, cls=True)

        # 检查结果格式并适当处理
        if not results or len(results) == 0:
            return "未能识别到文字内容"

        # PaddleOCR返回格式可能因版本不同而异
        # 新版本结构: results是一个list，包含每页的识别结果
        if isinstance(results[0], list):
            # 提取所有识别的文字内容
            text_result = ""
            for idx, page_result in enumerate(results):
                if page_result:
                    page_text = []
                    for line in page_result:
                        if len(line) >= 2:
                            confidence = line[1][1]  # 置信度
                            text = line[1][0]  # 文字内容
                            if confidence > 0.7:  # 只保留置信度较高的结果
                                page_text.append(text)

                    if page_text:
                        text_result += f"--- 页面 {idx + 1} ---\n"
                        text_result += "\n".join(page_text) + "\n\n"

            return text_result.strip() if text_result else "未能识别到高置信度的文字内容"
        else:
            # 旧版本结构处理
            recognized_texts = []
            for line in results:
                if isinstance(line, list) and len(line) >= 2:
                    if isinstance(line[1], tuple) and len(line[1]) >= 1:
                        recognized_texts.append(line[1][0])

            return "\n".join(recognized_texts) if recognized_texts else "未能识别到文字内容"

    except Exception as e:
        logging.error(f"解析图片 {file_path} 时出错: {str(e)}")
        return f"解析图片时出错: {str(e)}"


def parse_excel(file_path):
    """解析 Excel 文件"""
    try:
        df = pd.read_excel(file_path)
        return df.to_string()
    except Exception as e:
        logging.error(f"解析 Excel 文件 {file_path} 时出错: {str(e)}")
        return f"解析 Excel 文件时出错: {str(e)}"


def parse_json(file_path):
    """解析 JSON 文件"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        return json.dumps(data, ensure_ascii=False, indent=2)
    except Exception as e:
        logging.error(f"解析 JSON 文件 {file_path} 时出错: {str(e)}")
        return f"解析 JSON 文件时出错: {str(e)}"


def parse_html(file_path):
    """解析 HTML 文件"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return f.read()
    except Exception as e:
        logging.error(f"解析 HTML 文件 {file_path} 时出错: {str(e)}")
        return f"解析 HTML 文件时出错: {str(e)}"


def parse_markdown(file_path):
    """解析 Markdown 文件"""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            return f.read()
    except Exception as e:
        logging.error(f"解析 Markdown 文件 {file_path} 时出错: {str(e)}")
        return f"解析 Markdown 文件时出错: {str(e)}"


def define_prompt(question, file_contents=None):
    """定义提示词模版，可以包含文件内容"""
    if file_contents:
        return f"""请基于以下文件内容回答用户的问题：
        文件内容：
        {file_contents}

        用户问题：{question}

        """
    else:
        return f"""请回答用户的问题：{question}
        """


# 保留思考标签而不过滤
def filter_think_tags(text):
    """保留思考标签的内容，只移除标签本身"""
    # 移除<think>标签但保留内容
    result = re.sub(r'<think>(.*?)</think>', r'\1', text, flags=re.DOTALL)
    return result


# 原始过滤思考标签函数（已注释）
# def filter_think_tags_original(text):
#     """删除文本中的<think>xxx</think>标签及其内容"""
#     result = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL)
#     print('Result:', result)
#     return result

@app.get("/")
async def index(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})


@app.post("/api/chat")
async def chat(
        message: str = Form(...),
        files: list[UploadFile] = File(None),
        background_tasks: BackgroundTasks = None
):
    try:
        # 处理上传的文件
        file_results = None
        if files:
            valid_files = [f for f in files if f and f.filename and allowed_file(f.filename)]
            if valid_files:
                file_results = await parse_files_async(valid_files)
                all_file_contents = file_results["content"]
            else:
                all_file_contents = None
        else:
            all_file_contents = None

        # 创建提示词
        my_pmt = define_prompt(message, all_file_contents)
        logger.info(f'生成提示词: 长度={len(my_pmt)}字符')

        # 流式响应
        async def generate():
            collected_chunks = []

            # 创建流式响应 买了硅基流动，没有买阿里云的，只有阿里云的能关闭思考模式
            stream = client.chat.completions.create(
                model="Qwen/Qwen3-14B",  # Qwen/Qwen2.5-14B-Instruct Qwen/Qwen3-14B
                messages=[
                    {"role": "system", "content": "You are a helpful assistant."},
                    {"role": "user", "content": my_pmt}
                ],
                stream=True,
                temperature=0.7,
                max_tokens=2000
            )

            # 处理流式响应
            for chunk in stream:
                if chunk.choices[0].delta.content:
                    content = chunk.choices[0].delta.content
                    collected_chunks.append(content)
                    yield content

            logger.info(f"流式输出完成，总内容长度: {len(''.join(collected_chunks))}字符")

        return StreamingResponse(
            generate(),
            media_type='text/event-stream'
        )

    except Exception as e:
        logger.error(f"Error in chat API: {str(e)}")
        return JSONResponse(
            status_code=500,
            content={"response": f"发生错误: {str(e)}"}
        )


@app.post("/api/case-review")
async def case_review(file: UploadFile = File(...), prompt: str = Form(None), background_tasks: BackgroundTasks = None):
    # def case_review(file: UploadFile = File(...), background_tasks: BackgroundTasks = None):  # 去掉异步方便演示
    """处理案件审查请求"""
    # try:
    # 检查文件类型
    if not file.filename.lower().endswith('.zip'):
        return JSONResponse(
            status_code=400,
            content={"success": False, "error": "只支持ZIP格式的文件"}
        )

    # 处理ZIP文件 await: 程序会暂停执行，直到 process_zip 方法完成所有文件解析和处理后
    # result = case_processor.process_zip(file, background_tasks)
    result = await case_processor.process_zip(file, background_tasks)
    # 场景一: 比较多文件日期顺序不一致问题
    # 场景二:
    # 场景处理器
    # scene_handlers = [scene1_handler, scene2_handler, scene3_handler]
    scene_handlers = [scene3_handler]
    tasks = [handler(result, prompt) for handler in scene_handlers]
    await asyncio.gather(*tasks)

    # 总结分析结果
    result["analysis"] = "正在分析文件，请稍后查看结果"
    # 拼接所有 analysis_sceneX 字段内容
    analysis_all = ""
    for k in sorted(result.keys()):
        if k.startswith("analysis_scene"):
            analysis_all += f"{k}:{result[k]}"
    resp = analysis_all.strip()

    # 处理完后自动清理
    case_processor.cleanup()

    if result["success"]:
        return JSONResponse(
            content={"result": resp}
        )
    else:
        return JSONResponse(
            status_code=500,
            content={"success": False, "error": result["error"]}
        )

    # except Exception as e:
    #     return JSONResponse(
    #         status_code=500,
    #         content={"success": False, "error": str(e)}
    #     )


async def scene1_handler(result, prompt):
    # 场景一: 比较多文件日期顺序不一致问题
    # 获取指定文件名对应的value
    file_names = ["1-信访办理呈批表20290114.xlsx", "2-线索办理呈批表20290114.xlsx", "3-信访了结呈批表20290114.xlsx",
                  "4-线索了结呈批表20290114.xlsx"]
    file_values = []
    for name in file_names:
        value = case_processor.get_file_content(name)
        file_values.append(value)
    # 调用llm进行问答
    system = f"根据{file_values}回答用户问题" + prompt
    # system = f"根据{file_values}回答用户问题" \
    #          "编号1.信访办理呈批表、编号2.线索办理呈批表、编号3.信访了结呈批表、编号4.线索了结呈批表，" \
    #          "先不比较表名日期，只比较不同表之间的收到日期的顺序，各个表的收到日期需要从前到后的顺序，提示词编号最小的日期最小！不要帮我排序，不要管信访编号，只需要判断收到日期即可，告诉我哪些表出了问题即可；,每个表内的日期，都要比表名的日期早，有问的表帮我列出来"
    # 创建非流式响应
    response = call_llm(system)
    # 修复Stream对象没有choices属性的问题
    result["analysis_scene1"] = response.choices[0].message.content


async def scene2_handler(result, prompt):
    file_names = ["1.1-立案呈批报告表-XX姓名（三级经理）20220114.docx"]
    file_values = []
    for name in file_names:
        value = case_processor.get_file_content(name)
        file_values.append(value)
    # 调用llm进行问答
    system = f"""根据{file_values}回答用户问题," \
                 "规则1：中央八项规定精神是2012年12月中共中央政治局提出的作风建设核心要求，其本质是厉行勤俭节约、反对奢侈浪费和奢靡之风、廉洁从政、工作作风务实。它从中央政治局扩展到全党，成为纠治"四风"（形式主义、官僚主义、享乐主义、奢靡之风）的总纲领。 违反中央八项规定精神的行为主要包括两类： 1.形式主义、官僚主义：如贯彻决策不力、脱离群众、弄虚作假等； 2.享乐主义、奢靡之风：如违规公款吃喝、收送礼金、公款旅游、超标准接待等。 例如，虚构接待报销公款即属"虚列开支套取资金"，违反廉洁从政要求，对应《纪律处分条例》第九十六条和第一百一十三条。中央八项规定精神通过制度化约束和典型案例曝光持续深化，成为全面从严治党的重要抓手。 扣分细则：违反中央八项规定精神，第87条，扣0.2分，多处累计扣分，最多扣 2 分。规则2：立案金额超过标准，未说明触发立案依据，扣分1；规则3：纪律审查室拟办意见未提集团层级报批与监察权归属，程序缺失，扣分1"""
    # 创建非流式响应
    response = call_llm(system)
    # 修复Stream对象没有choices属性的问题
    result["analysis_scene2"] = response.choices[0].message.content


async def scene3_handler(result, prompt):
    """
    场景3：信息填写不完整（针对中国电信纪委表格结构优化版）
    使用大模型判断缺失字段，每个缺失字段扣0.2分，最多扣1分
    """
    file_names = ["2-线索办理呈批表20290114.xlsx"]

    # 获取文件内容
    file_values = []
    for name in file_names:
        value = case_processor.get_file_content(name)
        file_values.append(value)

    # 构建系统提示词，要求大模型分析缺失字段
    system = f"""请分析以下表格内容，判断是否存在未填写的关键字段。
                表格内容：{file_values}

                请按照以下要求进行分析：
                1. 检查所有必填字段是否已填写
                2. 对于空值、null、undefined、空字符串等视为未填写
                3. 请列出所有未填写的字段名称
                4. 如果所有字段都已填写，请回复"所有字段已完整填写"

                请直接列出缺失的字段名称，用逗号分隔。"""

    # 调用大模型进行分析
    response = call_llm(system)
    missing_fields = response.choices[0].message.content.strip()

    # 处理分析结果
    if missing_fields and missing_fields != "所有字段已完整填写":
        # 将返回的字符串转换为列表
        missing_fields_list = [field.strip() for field in missing_fields.split(',')]

        # 计算扣分
        deduction_per_field = 0.2  # 每个字段扣0.2分
        max_deduction = 1.0  # 最多扣1分
        score_deduction = min(len(missing_fields_list) * deduction_per_field, max_deduction)

        result["analysis_scene3"] = (
            f"《线索办理呈批表》缺失关键字段：[{', '.join(missing_fields_list)}]，"
            f"扣{score_deduction:.1f}分"
        )
    else:
        result["analysis_scene3"] = "表格填写完整，符合审查要求"

    print(f"[DEBUG] 缺失字段检测结果: {missing_fields}")
    print(f"[DEBUG] 扣分情况: {score_deduction:.1f}分")


def call_llm(system):
    response = client.chat.completions.create(
        model="Qwen/Qwen3-14B",
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": "请帮我分析一下，谢谢！"}
        ],
        stream=False,
        temperature=0,
        max_tokens=4096  # 最大生成令牌数，根据需要调整 Qwen3可以8196
    )
    return response


@app.get("/api/analysis-status")
async def analysis_status():
    """检查分析结果是否完成"""
    # 思路,可以存到查es,前端传一个唯一标识(后端也行UUID啥的)
    try:
        # 假设分析结果存储在全局变量中，这里使用一个示例状态
        analysis_status = {
            "scene1": "completed",  # 场景一分析完成
            "scene2": "pending"  # 场景二分析未完成
        }
        return JSONResponse(content={"status": analysis_status})
    except Exception as e:
        return JSONResponse(
            status_code=500,
            content={"error": str(e)}
        )


@app.get("/index_case")
async def index_case(request: Request):
    return templates.TemplateResponse("index_case.html", {"request": request})


if __name__ == '__main__':
    uvicorn.run(app, host="0.0.0.0", port=8000)